from threading import Thread
import math
import random
import socket
import sys
import pickle

#SEND PUBLIC KEY AS STRINGS

#needs send and receive func for keys
#send public key p
#functions for cryptography
def primeSieve(n):
    p=2
    total=[True]*n
    total[0]=False
    total[1]=False
    i=0
    while i<=n:
            
        if i<len(total) and total[i]==True:
            for j in range(i*i,n,i):
                total[j]=False
        i=i+1
    
    c=0
    prime=[]
    while c<n:
        if total[c]==True:
            #  print c,
            prime.append(c)
        c+=1
    #prime is an array of all primes less than n
    x=random.randint(5000, 50000)
    i=random.randint(5000, 50000)
    
    a=prime[x]
    b=prime[i]

    return ((a, b))
    
def gcd (a, b):

  if b == 0: return a
  else: return gcd(b, a % b)

def multiplicative_inverse(a, b):
    
    
    origA = a
    X = 0
    prevX = 1
    Y = 1
    prevY = 0

    while b != 0:

        temp = b
        quotient = a/b
        b = a % b
        a = temp
        temp = X
        a = prevX - quotient * X
        prevX = temp
        temp = Y
        Y = prevY - quotient * Y
        prevY = temp

    return origA + prevY

def generateRSAKeys(p, q):
  
  n = p * q
  
  m = (p - 1) * (q - 1)

  e = 3

  while 1:
    
      if gcd(m, e) == 1: break
      else: e = e + 2

  
  d = multiplicative_inverse(m, e) 

  
  return ((n,e), (n,d))

def modulo(encrypted_num, d, n):
    x=1
    y=encrypted_num
    while d>0:
        if d%2==1:
            x=(x*y)%n
        y=(y*y)%n
        d/=2
    #x=decrypted numerical value
    return x%n
                      
def startEncrypt():
    #(a, b)=primeSieve(5000000)
    a=244637#use for presentation
    b=258551#use for presentation
    (publickey, privatekey) = generateRSAKeys(a, b)
    n, e = publickey
    n, d = privatekey
    print "Public: ", publickey
    print "Private: ", privatekey
    return ((e, d, n))

def encrypt(input_num, e, c):
    encrypted_num = (input_num ** e) % c

    return encrypted_num

def decrypt(pickled, d, n):
    depickled = pickle.loads(pickled)#pickle
    decryptArray=[]
    decryptStr=""
    for x in depickled:
        decrypted_num=modulo(x, d, n)
        recharred=reChar(decrypted_num)
        decryptStr+=recharred
        
    return decryptStr

def numVal(n, eP, cP):
    length=len(n)
    enArray=[]
    for i in range(0, length):
        input_num=ord(n[i])
        encrypted_num=encrypt(input_num, eP, cP)
        enArray.append(encrypted_num)
    pickled = pickle.dumps(enArray)    
   
    return pickled

def reChar(n):
    decrypt=chr(n)
    return decrypt

def sendKey(client_socket, e, c):
    client_socket.send(str(e))
    client_socket.send(str(c))

def recvKey(client_socket):
    e=client_socket.recv(4096)
    c=client_socket.recv(4096)
    return ((int(e), int(c)))
    
    
def main():
    print "Generating public and private keys...."
    (e, d, c)= startEncrypt()
    q = raw_input("1 for client, 2 for server: ")
    if (q == '1'):
        client(e, d, c)

    elif (q == '2'):
        server(e, d, c)

    else:
        print "try again"

def client(e, d, c):
    print "New Client"
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print "Connecting"
    client_socket.connect(("144.118.146.198", 3255))
    print "Connected"
    (eP, cP)=recvKey(client_socket)
    sendKey(client_socket, e, c)
    #client_socket.send(str(e))
    #client_socket.send(str(c))
    #(eP, cP)=client_socket.recv(4096)
    t3=Thread(target=receive, args=(client_socket, d, c,))
    t4=Thread(target=send, args=(client_socket, eP, cP,))
    t3.start()
    t4.start()
    while 1:
        t3.join()
        t4.join()
        break;

def server(e, d, c):
    client_socket= socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print "Binding"
    client_socket.bind(("",3255))
    print "Listening"
    client_socket.listen(5)
    comm_client, address=client_socket.accept()
    print "Connection from:", address
    #comm_client.send(str(e))
    #comm_client.send(str(c))
    #(eP, cP)=comm_client.recv(4096)
    sendKey(comm_client, e, c)
    (eP, cP)=recvKey(comm_client)
    t5=Thread(target=send, args=(comm_client,eP, cP,))
    t6=Thread(target=receive, args=(comm_client, d, c,))#passing other client's public key along with your private key
    t5.start()
    t6.start()
    while 1:
        t5.join()
        t6.join()
        break;

def send(client_socket, eP, cP):
    while 1:
        data = raw_input ( "SEND (Type \quit to Quit):")
        doto=numVal(data, eP, cP)
        
        if (data <> '\quit' and data <> '\quit'):
            client_socket.send(doto)
        else:
            doto=numVal(data, eP, cP)
            client_socket.send(doto)
            
            client_socket.close()
            break;

def receive(client_socket, d, c):
    
    while 1:
        data = client_socket.recv(512)
        doto=decrypt(data, d, c)
        if ( data == '\quit' or data == '\quit'):
            client_socket.close(data)
            break;
        else:
            
            print "\nReceived:", doto
    
 
main()


